• 对于非volatile类型的64位数值变量(double和long),JVM允许将64位的读操作和写操作分解为两个32位的操作。当读取一个非volatile类型的long变量时,如果对变量的读操作和写操作在不同的线程中执行,那么很可能会读取到某个值的高32位和另一个值的低32位。

    在多线程中使用共享且可变的long和double类型的变量是不安全的,除非使用关键字volatile来声明,或者使用锁进行保护

  • 加锁的含义不仅仅局限于互斥行为,还包括内存可见性,为了确保所有线程都能看到共享变量的最新值,所有执行读操作或者写操作的线程都必须在同一个锁上进行同步

  • volatile变量用来确保将变量的更新操作通知到其他线程,当把变量声明为volatile类型后,编译器与运行时都会注意到这个变量是共享的,因此不会将该变量上的操作同其他内存操作一起重排序,volatile类型变量不会被缓存在寄存器或者对其他处理器不可见的地方,因此在读取volatile类型的变量时总会返回最新写入的值。

  • 加锁机制既可以确保可见性又可以确保原子性,而volatile变量只能确保可见性

  • 看下面这段代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    public class ThisEscape {
    public ThisEscape(EventSource source) {
    source.registerListener(new EventListener() { // 匿名内部类会持有一个外部类的引用
    public void onEvent(Event e) {
    doSomething(e);
    }
    });
    }
    }

    在ThisEscape中给出了逸出的一个特殊示例,即this引用在构造函数中逸出,当内部的EventListener实例发布时,在外部封装的ThisEscape实例也逸出了。当且仅当对象的构造函数返回时,对象才处于可预测的一致的状态,因此,当从对象的构造函数中发布对象时,只是发布了一个尚未构造完成的对象,即使发布对象的语句在构造函数最后一行也是如此。

    如果this引用在构造过程中逸出,那么这种对象就被认为是不正确构造。

    不要在构造函数中使this引用逸出。

    在构造函数中使this逸出的一个常见错误是在构造函数中启动一个线程,当对象在其构造函数中创建一个线程时,无论是显示创建还是隐式创建,this引用都会被新创建的线程共享。在对象尚未完全构造之前,新的线程就可以看见它。

    在构造函数中创建线程并没有错误,但最好不要立刻启动它,而是通过一个start或initialize方法来启动。

  • 发布一个对象的意思是指使对象在当前作用域之外的代码中使用,例如将一个指向该对象的引用保存在其他代码可以访问的地方,或者在某一个非私有的方法中返回该引用,或者将引用传递到其他类的方法中。

  • 如果想在构造函数中注册一个事件监听器或启动线程,那么可以使用一个私有的构造函数和一个公共的工厂方法,从而避免不正确的构造过程:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    public class SaleListener {
    private final EventListener listenerl;
    privaet SafeListener() {
    listener = new EventListener() { // 虽然内部类中持有了外部类的引用,但是listener并没有在构造函数中发布出去,而是在newInstance()函数中才发布出去,此时外部类已经构造完成。
    public void onEvent(Event e) {
    doSomething(e);
    }
    };
    }
    public static SafeListener newInstance(EventSource source) {
    SafeListener safe = new SafeListener();
    source.registerListener(safe.listener);
    return safe;
    }
    }

    只有在构造函数返回时,this引用才应该从线程中逸出。构造函数可以将this引用保存到某个地方,只要其他线程不会在构造函数完成之前使用它。

  • 线程封闭:线程封闭的对象只能由一个线程拥有,对象被封闭在该线程中,并且只能由这个线程修改

  • 只读共享:在没有额外同步的情况下,共享的只读对象可以由多个线程并发访问,但任何线程都不能修改它。

  • 线程安全共享:线程安全的对象在其内部实现同步,因此多个线程可以通过对象的公有接口来进行访问而不需要进一步的访问